import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Like, Repository } from 'typeorm';
import {
  EvaluationComposeCreateDto,
  EvaluationComposeIndexDto,
  EvaluationComposeUpdateDto,
} from './evaluation-compose.dto';
import { EvaluationComposeModel } from './evaluation-compose.model';
import { omitUndefined } from "@/common/utils/utils";
import { EvaluationModel } from "@/module/evaluation/evaluation.model";
import { EvaluationStatus } from "common/utils/data";
import { EvaluationProjectModel } from "@/module/evaluation-project/evaluation-project.model";
import { EvaluationDataService } from "@/module/evaluation-data/evaluation-data.service";
import { BizFailedException } from "@/common/biz-failed/biz-failed.exception";
import { BizFailedCodeEnum } from "@/common/biz-failed/biz-failed.enum";
import { EvaluationComposeShowResult } from "@/module/evaluation-compose/evaluation-compose.result";
import { EvaluationDataModel } from "@/module/evaluation-data/evaluation-data.model";
import NP from 'number-precision';
import { findNearestTargetRow } from "common/utils/utils";

@Injectable()
export class EvaluationComposeService {
  constructor(
    @InjectRepository(EvaluationComposeModel)
    private readonly evaluationComposeModelRepository: Repository<EvaluationComposeModel>,
    @InjectRepository(EvaluationModel)
    private readonly evaluationModelRepository: Repository<EvaluationModel>,
    @InjectRepository(EvaluationProjectModel)
    private readonly evaluationProjectModelRepository: Repository<EvaluationProjectModel>,
    private readonly evaluationDataService: EvaluationDataService,
  ) {}

  async index(query: EvaluationComposeIndexDto, userUuid: string) {
    const queryBuilder = this.evaluationComposeModelRepository
      .createQueryBuilder('evaluationCompose')
      .leftJoinAndSelect('evaluationCompose.earbudsEvaluation', 'earbudsEvaluation')
      .leftJoinAndSelect('evaluationCompose.soundSourceEvaluation', 'soundSourceEvaluation')
      .select([
        'evaluationCompose.uuid',
        'evaluationCompose.title',
        'evaluationCompose.earbudsEvaluationUuid',
        'evaluationCompose.earbudsEvaluationDataGroup',
        'evaluationCompose.soundSourceEvaluationUuid',
        'evaluationCompose.soundSourceEvaluationDataGroup',
        'evaluationCompose.updateTime',
        'earbudsEvaluation.title',
        'soundSourceEvaluation.title',
      ])
      .andWhere(`(evaluationCompose.userUuid = '${userUuid}')`)

    if (query.keyword) {
      queryBuilder.andWhere('(evaluationCompose.title ILIKE :keyword OR earbudsEvaluation.title ILIKE :keyword OR soundSourceEvaluation.title ILIKE :keyword)', {
        keyword: `%${query.keyword}%`,
      });
    }

    if(query.uuids){
      queryBuilder.andWhere('(evaluationCompose.uuid IN (:...uuids))', {
        uuids: query.uuids.split(','),
      });
    }

    query.wrapQueryBuilder(queryBuilder,'evaluationCompose')

    return queryBuilder.getManyAndCount();
  }

  async show(uuid: string, userUuid: string) {
    const evaluationCompose = await this.evaluationComposeModelRepository.findOne({
      where: {
        uuid,
        userUuid,
      },
    });
    if (!evaluationCompose) {
      throw new BizFailedException(BizFailedCodeEnum.EVALUATION_COMPOSE_NOT_EXISTS);
    }

    const projects = await this.getComposeProjects();

    const res = (await this.evaluationComposeModelRepository
      .createQueryBuilder('evaluationCompose')
      .andWhere('(evaluationCompose.uuid = :uuid)', { uuid })
      .leftJoinAndSelect('evaluationCompose.user', 'user')
      .leftJoinAndSelect('evaluationCompose.earbudsEvaluation', 'earbudsEvaluation', `(earbudsEvaluation.status = '${EvaluationStatus.PUBLISH}')`)
      .leftJoinAndSelect('evaluationCompose.soundSourceEvaluation', 'soundSourceEvaluation', `(soundSourceEvaluation.status = '${EvaluationStatus.PUBLISH}')`)

      .leftJoinAndSelect('earbudsEvaluation.brand', 'earbudsEvaluationBrand')
      .leftJoinAndSelect('earbudsEvaluation.datas', 'earbudsEvaluationDatas')
      .leftJoinAndSelect('soundSourceEvaluation.brand', 'soundSourceEvaluationBrand')
      .leftJoinAndSelect('soundSourceEvaluation.datas', 'soundSourceEvaluationDatas')
      .select([
        'evaluationCompose.uuid',
        'evaluationCompose.title',
        'evaluationCompose.earbudsEvaluationUuid',
        'evaluationCompose.earbudsEvaluationDataGroup',
        'evaluationCompose.soundSourceEvaluationUuid',
        'evaluationCompose.soundSourceEvaluationDataGroup',
        'evaluationCompose.updateTime',
        'earbudsEvaluation.title',
        'soundSourceEvaluation.title',

        'user.uuid',
        'user.name',
        'user.avatar',
        'user.title',
        'user.titleCertification',

        'earbudsEvaluationBrand.title',
        'earbudsEvaluationBrand.img',
        'soundSourceEvaluationBrand.title',
        'soundSourceEvaluationBrand.img',

        'earbudsEvaluationDatas.uuid',
        'earbudsEvaluationDatas.data',
        'earbudsEvaluationDatas.defaultShow',
        'earbudsEvaluationDatas.img',
        'earbudsEvaluationDatas.json',
        'earbudsEvaluationDatas.desc',
        'earbudsEvaluationDatas.name',
        'earbudsEvaluationDatas.group',
        'earbudsEvaluationDatas.sort',
        'earbudsEvaluationDatas.updateTime',
        'earbudsEvaluationDatas.projectUuid',

        'soundSourceEvaluationDatas.uuid',
        'soundSourceEvaluationDatas.data',
        'soundSourceEvaluationDatas.defaultShow',
        'soundSourceEvaluationDatas.img',
        'soundSourceEvaluationDatas.json',
        'soundSourceEvaluationDatas.desc',
        'soundSourceEvaluationDatas.name',
        'soundSourceEvaluationDatas.group',
        'soundSourceEvaluationDatas.sort',
        'soundSourceEvaluationDatas.updateTime',
        'soundSourceEvaluationDatas.projectUuid',
      ])
      .getOne())!;

    const {datas:earbudsEvaluationDatas , ...restEarbudsEvaluation} = res.earbudsEvaluation;
    const {datas:soundSourceEvaluationDatas , ...restSoundSourceEvaluation} = res.soundSourceEvaluation ?? {};

    const currentRes: EvaluationComposeShowResult = {
      ...res,
      earbudsEvaluation: restEarbudsEvaluation,
      soundSourceEvaluation: Object.keys(restSoundSourceEvaluation).length ? restSoundSourceEvaluation : null,
      datas: this.getEvaluationComposeDatas({
        earbudsEvaluationDatas,
        earbudsEvaluationDataGroup: res.earbudsEvaluationDataGroup,
        soundSourceEvaluationDatas,
        soundSourceEvaluationDataGroup: res.soundSourceEvaluationDataGroup,
        projects,
      }),
    };
    return currentRes;
  }

  async create(body: EvaluationComposeCreateDto, userUuid: string): Promise<void> {
    await this.evaluationComposeModelRepository.save({
      ...body,
      userUuid,
    });
  }

  async update(body: EvaluationComposeUpdateDto, uuid: string): Promise<void> {
    await this.evaluationComposeModelRepository.update(
      {
        uuid,
      },
      omitUndefined(body),
    );
  }

  async destroy(uuid: string): Promise<void> {
    await this.evaluationComposeModelRepository.delete({
      uuid,
    });
  }

  private getComposeProjects(){
    return this.evaluationProjectModelRepository.find({
      select: [
        'uuid',
        'name',
        'desc',
        'rule',
        'type',
        'code',
      ],
      where: {
        code: In(['RAW_FR', 'THD', 'PHASE'])
      }
    });
  }

  getEvaluationComposeDataSet({
    earbudsEvaluationDataSet,
    soundSourceEvaluationDataSet,
                                projectCode,
  }: {
    earbudsEvaluationDataSet: string[][],
    soundSourceEvaluationDataSet?: string[][],
    projectCode: string;
  }){
    if(!soundSourceEvaluationDataSet){
      return earbudsEvaluationDataSet;
    }
    function getUniqueKey (xValue: string, y: string){
      return `${xValue}_|huihifi|_${y}`
    }

    function dataSetToMap(dataSet: string[][]){
      const [titleRow,...restRows] = dataSet;

      const res: Record<string, string> = {};
      restRows.forEach(row=>{
        const [xValue,...restCells] = row;

        restCells.forEach((cell, cellIndex) =>{
          res[getUniqueKey(xValue, titleRow[cellIndex + 1])] = cell;
        })
      })

      return res;
    }

    // const earbudsEvaluationDataSetMap = dataSetToMap(earbudsEvaluationDataSet);
    const soundSourceEvaluationDataSetMap = dataSetToMap(soundSourceEvaluationDataSet);

    // 通用补偿逻辑
    let earbudsEvaluationRepairRow: string[] | undefined;
    let soundSourceEvaluationRepairRow: string[] | undefined;
    if(projectCode === 'RAW_FR'){ // 频响要把1Kz纵坐标置为0，等于说减去1khz的值
      earbudsEvaluationRepairRow = findNearestTargetRow(earbudsEvaluationDataSet, '1000');
      soundSourceEvaluationRepairRow = findNearestTargetRow(soundSourceEvaluationDataSet, '1000');
    }

    let titleRow: string[];
    return earbudsEvaluationDataSet.map((row, rowIndex)=>{
      if(rowIndex ===0){
        titleRow = row;
        return row;
      }
      let xValue: string;
      return row.map((cell, cellIndex)=>{
        if(cellIndex === 0){
          xValue = cell;
          return cell;
        }
        return NP.strip(
          Number(cell) + Number(soundSourceEvaluationDataSetMap[getUniqueKey(xValue, titleRow[cellIndex])])
          - (earbudsEvaluationRepairRow ? Number(earbudsEvaluationRepairRow[cellIndex]) : 0)
          - (soundSourceEvaluationRepairRow ? Number(soundSourceEvaluationRepairRow[cellIndex]) : 0)
        ).toString();
      })
    });
  }


  private getEvaluationComposeDatas({
    earbudsEvaluationDatas,
    soundSourceEvaluationDatas,
    earbudsEvaluationDataGroup,
    soundSourceEvaluationDataGroup,
    projects,
  }: {
    earbudsEvaluationDatas: EvaluationDataModel[],
    soundSourceEvaluationDatas?: EvaluationDataModel[],
    earbudsEvaluationDataGroup: string,
    soundSourceEvaluationDataGroup?: string,
    projects: EvaluationProjectModel[]
  }){
    const earbudsEvaluationDatasRes = this.evaluationDataService.getEvaluationDatasBySourceEvaluationDataModel(
      earbudsEvaluationDatas.filter(item=>
        item.group === earbudsEvaluationDataGroup && projects.some(project=>project.uuid === item.projectUuid)
      ),
      projects,
    );
    if(!soundSourceEvaluationDatas || !soundSourceEvaluationDataGroup){
      return earbudsEvaluationDatasRes;
    }

    const soundSourceEvaluationDatasRes = this.evaluationDataService.getEvaluationDatasBySourceEvaluationDataModel(
      soundSourceEvaluationDatas.filter(
        item=>item.group === soundSourceEvaluationDataGroup && projects.some(project=>project.uuid === item.projectUuid)
      ),
      projects,
    );
    return earbudsEvaluationDatasRes.map(earbudsEvaluationDataItem=>{
      const matchSoundSourceEvaluationDataItem = soundSourceEvaluationDatasRes.find(it=> it.projectUuid === earbudsEvaluationDataItem.projectUuid);
      if(!matchSoundSourceEvaluationDataItem){
        return earbudsEvaluationDataItem;
      }

      const earbudsEvaluationDataItemChildrenItem = earbudsEvaluationDataItem.children[0];
      const matchSoundSourceEvaluationDataItemChildrenItem = matchSoundSourceEvaluationDataItem.children[0];

      const dataSet = this.getEvaluationComposeDataSet({
        earbudsEvaluationDataSet: earbudsEvaluationDataItemChildrenItem.dataSet,
        soundSourceEvaluationDataSet: matchSoundSourceEvaluationDataItemChildrenItem.dataSet,
        projectCode: earbudsEvaluationDataItem.project.code,
      });

      return {
        ...earbudsEvaluationDataItem,
        // 这里合成的children肯定只有一个，因为dataGroup的设定
        children: [{
          ...earbudsEvaluationDataItemChildrenItem,
          name: [
            {value: earbudsEvaluationDataItemChildrenItem.name,label: `耳机 ${earbudsEvaluationDataItemChildrenItem.name}`},
            {value: matchSoundSourceEvaluationDataItemChildrenItem.name,label: `音源 ${matchSoundSourceEvaluationDataItemChildrenItem.name}`},
          ].filter(item=>item.value).map(item=>item.label).join(' + '),
          desc: [
            {value: earbudsEvaluationDataItemChildrenItem.desc,label: `耳机 ${earbudsEvaluationDataItemChildrenItem.desc}`},
            {value: matchSoundSourceEvaluationDataItemChildrenItem.desc,label: `音源 ${matchSoundSourceEvaluationDataItemChildrenItem.desc}`},
          ].filter(item=>item.value).map(item=>item.label).join('\n'),
          dataSet
        }]
      }
    })
  }
}
